package dse.modules;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import testing.TestHelper;
import dse.DseException;
import dse.ExperimentConfiguration;
import dse.Network;
import dse.Node;
import dse.NodeState;
import dse.Pair;
import dse.modules.eln.EarthLikeNetwork;

public class GeneralRequestUpdateEventScheduler 
implements RequestUpdateEventScheduler
{	
	private int period;
	private LinkedList<Pair<Node,Integer>> nodeSchedule;  
	
	private int periodsBetweenUpdates;
	private int startupPeriods;	

	public void initalize (
			ExperimentConfiguration systemSettings,
			Map<String, String> moduleSettings
			)
	throws DseException
	{
		period = 0;
		nodeSchedule = new LinkedList<Pair<Node,Integer>>();
		
		ModuleUtil.SettingsReader sr = ModuleUtil.getSettingsReader(
				this.getClass(), moduleSettings
				);
		periodsBetweenUpdates = sr.getInteger("periods-between-updates");
		startupPeriods = sr.getInteger("startup-periods");
	}


	/**
	 * Gets the next round of updates that should be made.  It returns the nodes
	 * that should contact their super node with a caching request update 
	 * message.
	 * 
	 * @param network			The experiment network.
	 * @return	The set of requests to initiate for a single time period.
	 */
	public List<Node> getRequests (Network network) {
		period++;
		if (period == 1) {
			// Then we hvae to populate our schedule			
			initalizeSchedule(network);
		}
		
		LinkedList<Node> res = new LinkedList<Node>();
		
		// Build results
		Iterator<Pair<Node,Integer>> pi = nodeSchedule.iterator();
		while (pi.hasNext()) {
			Pair<Node,Integer> p = pi.next();
			if (p.second <= period) {
				pi.remove();
				res.add(p.first);
			} else break;
		}
		
		// Now read result nodes :)
		Iterator<Node> resI = res.iterator();
		while (resI.hasNext()) {
			Node n = resI.next();
			nodeSchedule.addLast(
					new Pair<Node,Integer>(n,period+periodsBetweenUpdates+1)
					);
			// And filter out any nodes that are down
			if (n.isUp() == false)
				resI.remove();
		}
		
		return res;
	}


	private void initalizeSchedule (Network network) {
		int nodesForStartupPeriod = network.nodeCount()/startupPeriods;
		
		int a = 0;
		int c = 1;
		for (Node n : network.nodes()) {
			if (n.isSupernode())
				continue;
			
			a++;
			nodeSchedule.add(new Pair<Node,Integer>(n,c));
			if (a >= nodesForStartupPeriod) {
				c++;
				a = 0;
			}
		}
	}	
	
	
	

	/**
	 * For testing :)
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main (String [] args) 
	throws Exception
	{
		if (args.length != 4 && args.length != 5) {
			System.out.println(
					"Usage: java -cp ... " +
					"dse.modules.GeneralRequestUpdateEventScheduler " +
					"<nodes> <max-cluster-size> " +
					"<periods-between-updates> <startup-periods> (seed)"
					);
			System.exit(-1);
		}
		
		
		ExperimentConfiguration ss = TestHelper.createSystemSettings(args, 4);
		
		EarthLikeNetwork network = TestHelper.createELNetwork(
				ss, args, 0, -1, -1
				);
		
		TestHelper.createClusters(ss, args, 1, network);
		
		GeneralRequestUpdateEventScheduler sched = 
			new GeneralRequestUpdateEventScheduler();

		Hashtable<String, String> props = new Hashtable<String, String>();
		props.put("periods-between-updates", args[2]);
		props.put("startup-periods", args[3]);
		
		sched.initalize(ss, props);
		
		for (Node n : network.nodes())
			n.setState(NodeState.Up);
		
		int rounds = Integer.parseInt(args[3])*3;
		
		for (int a = 0; a < rounds; a++) {
			System.out.println("Round #:" + (a+1));
			
			List<Node> requests = sched.getRequests(network);
			
			for (Node n : requests) {
				System.out.println(
						"Node " + n.id() + " (in " + n.clusterId() + ")"
						);
			}
			System.out.println();
			System.out.println();
		}
		
		System.out.println("Done.");
	}
}